package com.martin.ads.omoshiroilib.camera;

import android.app.Activity;
import android.graphics.ImageFormat;
import android.graphics.SurfaceTexture;
import android.hardware.Camera;
import android.media.CamcorderProfile;
import android.media.MediaRecorder;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.util.Log;
import android.view.Surface;

import com.imageim.yimirror.common.EnviromentConfig;
import com.imageim.yimirror.common.event.EventCode;
import com.imageim.yimirror.common.event.EventMessage;
import com.martin.ads.omoshiroilib.glessential.CameraView;
import com.martin.ads.omoshiroilib.glessential.GLRender;
import com.martin.ads.omoshiroilib.util.FileUtils;
import com.martin.ads.omoshiroilib.util.Logger;

import org.greenrobot.eventbus.EventBus;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/**
 * Created by Ads on 2017/1/26.
 */
@SuppressWarnings("deprecation")
public class CameraEngine
        implements SurfaceTexture.OnFrameAvailableListener,
        Camera.AutoFocusCallback, Camera.PreviewCallback {

    private static final String TAG = "CameraEngine";
    public static double preferredRatio = 16.0 / 9;

    //    public static double preferredRatio = 16.0/9;
    static {
        if (!EnviromentConfig.IS_LANDSCAPE) {
            preferredRatio = 16.0 / 9;
            //            preferredRatio = 4.0 / 3;
        }
    }

    private SurfaceTexture mSurfaceTexture;
    private CameraView.RenderCallback renderCallback;
    private Camera camera;
    private Camera.Parameters mParams;
    private boolean cameraOpened;

    private static byte[] mBuffer;

    //frameWidth=size.redPacketHeight
    private int frameWidth;
    private int frameHeight;

    private static FakeMat[] mFrameChain;
    private int mChainIdx;
    private Thread mWorkerThread;
    private boolean mStopThread;
    private boolean mCameraFrameReady;
    public static float transRate = 2; // 分辨率不同导致人脸坐标的位置转换因子不同，例如camera的预览高度为1080时，计算出transRate=1080/540=2

    private GLRender.PictureTakenCallBack pictureTakenCallBack;
    private CameraView.PreviewSizeChangedCallback previewSizeChangedCallback;

    private MediaRecorder mMediaRecorder;
    private Camera.Size previewSize;
    private int currentCameraId;

    private double lastZoomValueRec;
    private int lastZoomValue;

    private GLRender glRender;

    public CameraEngine(double aspectRatio) {
        preferredRatio = aspectRatio;
        frameWidth = 1080;
        frameHeight = 1920;
        cameraOpened = false;

        mChainIdx = 0;
        if (mFrameChain == null) {
            mFrameChain = new FakeMat[2];
            mFrameChain[0] = new FakeMat();
            mFrameChain[1] = new FakeMat();
        }
    }

    public void setTexture(int mTextureID) {
        mSurfaceTexture = new SurfaceTexture(mTextureID);
        mSurfaceTexture.setOnFrameAvailableListener(this);
    }

    public long doTextureUpdate(float[] mSTMatrix) {
        try {
            mSurfaceTexture.updateTexImage();
        } catch (Exception e) {

        }
        mSurfaceTexture.getTransformMatrix(mSTMatrix);
        return mSurfaceTexture.getTimestamp();
    }

    public void openCamera(boolean facingFront) {
        synchronized (this) {
            int facing = facingFront ? Camera.CameraInfo.CAMERA_FACING_FRONT : Camera.CameraInfo.CAMERA_FACING_BACK;
            currentCameraId = getCameraIdWithFacing(facing);
            try {
                camera = Camera.open(currentCameraId);
            } catch (Exception e) {
                Log.e(TAG, "相机无法启动");
            }
            if (camera != null) {
                camera.setPreviewCallbackWithBuffer(this);
            }
            initRotateDegree(currentCameraId);
            if (camera != null) {
                mParams = camera.getParameters();
                List<Camera.Size> supportedPictureSizesList = mParams.getSupportedPictureSizes();
                //                List<Camera.Size> supportedVideoSizesList = mParams.getSupportedVideoSizes();
                List<Camera.Size> supportedPreviewSizesList = mParams.getSupportedPreviewSizes();
                Logger.logCameraSizes(supportedPictureSizesList);

                //                LogUtil.d("logCameraSizes", "============");
                //                Logger.logCameraSizes(supportedVideoSizesList);
                //                Logger.logCameraSizes(supportedPreviewSizesList);

                previewSize = choosePreferredSize(supportedPreviewSizesList, preferredRatio);
                Camera.Size photoSize = choosePreferredSize(supportedPictureSizesList, preferredRatio);
                if (EnviromentConfig.IS_LANDSCAPE) {
                    transRate = previewSize.height / 540.0f; // inputHeight/2
                } else {
                    //                    transRate = previewSize.redPacketWidth / 1280.0f; // sampleHeight
                    transRate = previewSize.width / 1380.0f; // sampleHeight
                }
                //                previewSize= getOptimalPreviewSize(supportedPreviewSizesList, 1920, 1080);
                //                Camera.Size photoSize= getOptimalPreviewSize(supportedPictureSizesList, 1920, 1080);

                // portrait
                //                frameHeight = previewSize.redPacketWidth;
                //                frameWidth = previewSize.redPacketHeight;
                // landscape
                frameHeight = previewSize.height;
                frameWidth = previewSize.width;
                Log.d(TAG, "openCamera: choose preview size :redPacketHeight==" + previewSize.height + "x" + previewSize.width);
                mParams.setPreviewSize(frameWidth, frameHeight);
                mParams.setPictureSize(photoSize.width, photoSize.height);

                mParams.setRecordingHint(true);// 12fps
                mParams.setAutoExposureLock(true);// 30fps
                mParams.setAutoWhiteBalanceLock(true);// 30fps
                if (isAutoFocusSupported(mParams)) {
                    mParams.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
                }
                mParams.setSceneMode(Camera.Parameters.SCENE_MODE_AUTO);
                //                mParams.setSceneMode(Camera.Parameters.SCENE_MODE_NIGHT_PORTRAIT);
                //                camera.cancelAutoFocus();
                //                mParams.setPreviewFrameRate(15);
                //                mParams.setZoom(0);
                //                camera.setFaceDetectionListener(new Camera.FaceDetectionListener() {
                //                    @Override
                //                    public void onFaceDetection(Camera.Face[] faces, Camera camera) {
                //                        if (myFaceDetectListener != null) {
                //                            myFaceDetectListener.onFaceList(faces);
                //                        }
                //                    }
                //                });

                int size = frameWidth * frameHeight;
                size = size * ImageFormat.getBitsPerPixel(mParams.getPreviewFormat()) / 8;
                if (mBuffer == null || mBuffer.length != size) {
                    mBuffer = new byte[size];
                }
                mFrameChain[0].init(size);
                mFrameChain[1].init(size);
                camera.addCallbackBuffer(mBuffer);
                camera.setParameters(mParams);
                //                setZoom(camera, 10);
                cameraOpened = true;
                //                focusCamera();
            }
        }
    }

    public static boolean isAutoFocusSupported(Camera.Parameters params) {
        List<String> modes = params.getSupportedFocusModes();
        return modes.contains(Camera.Parameters.FOCUS_MODE_AUTO);
    }

    private boolean isSupportZoom(Camera camera) {
        return camera.getParameters().isZoomSupported();
    }

    public void setZoom(Camera camera, int zoom) {
        if (isSupportZoom(camera)) {
            try {
                Camera.Parameters params = camera.getParameters();
                final int MAX = params.getMaxZoom();
                if (MAX == 0) {
                    return;
                }
                if (zoom <= MAX && zoom > 0) {
                    int zoomValue = params.getZoom();
                    Log.d("camera", "-----------------MAX:" + MAX + "   params : " + zoomValue);
                    params.setZoom(zoom);
                    camera.setParameters(params);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else {
            Log.e("camera", "Device not support zoom");
        }
    }

    public void startPreview() {
        lastZoomValueRec = lastZoomValue = 0;
        if (camera != null) {
            try {
                camera.setPreviewTexture(mSurfaceTexture);
            } catch (IOException e) {
                e.printStackTrace();
            }
            previewSizeChangedCallback.updatePreviewSize(frameWidth, frameHeight);
            camera.startPreview();
            //            if (camera.getParameters().getMaxNumDetectedFaces() > 0) {
            //                camera.startFaceDetection();
            //            }
            mCameraFrameReady = false;
            mStopThread = false;
            mWorkerThread = new Thread(new CameraWorker());
            mWorkerThread.start();
        }
    }

    public void stopPreview() {
        synchronized (this) {
            if (camera != null) {
                mStopThread = true;
                synchronized (this) {
                    this.notify();
                }
                mWorkerThread = null;
                camera.stopPreview();
            }
        }
    }

    public void releaseCamera() {
        synchronized (this) {
            if (camera != null) {
                camera.setPreviewCallback(null);
                camera.release();
                camera = null;
            }
            cameraOpened = false;
        }
    }

    public void switchCamera(boolean facingFront) {
        stopPreview();
        releaseCamera();
        openCamera(facingFront);
        startPreview();
    }

    @Override
    public void onAutoFocus(boolean success, Camera camera) {
        if (success) {
            camera.cancelAutoFocus();
        } else {
            focusCamera();
        }
    }

    @Override
    public void onFrameAvailable(SurfaceTexture surfaceTexture) {
        renderCallback.renderImmediately();
    }

    //Camera.CameraInfo.CAMERA_FACING_FRONT
    //Camera.CameraInfo.CAMERA_FACING_BACK
    public static int getCameraIdWithFacing(int facing) {
        Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
        int cameraCount = Camera.getNumberOfCameras();
        for (int camIdx = 0; camIdx < cameraCount; camIdx++) {
            Camera.getCameraInfo(camIdx, cameraInfo);
            if (cameraInfo.facing == facing) {
                return camIdx;
            }
        }
        return 0;
    }

    public static int getNumberOfCameras() {
        return Camera.getNumberOfCameras();
    }

    private boolean firstReceivedData = true;

    @Override
    public void onPreviewFrame(byte[] data, Camera camera) {
        synchronized (this) {
            mFrameChain[mChainIdx].putData(data);
            mCameraFrameReady = true;
            glRender.runOnDraw(GLRender.CMD_PROCESS_FRAME, mFrameChain[mChainIdx].getFrame(), camera);
            camera.addCallbackBuffer(mBuffer);
            this.notify();
        }
        //        Log.e("frame", camera.getParameters().getPreviewFrameRate() + " fps");
        if (firstReceivedData) {
            firstReceivedData = false;
            EventBus.getDefault().post(new EventMessage(EventCode.CODE_HIDE_CAMERA_ANIM, null));
        }
    }


    private class CameraWorker implements Runnable {
        @Override
        public void run() {
            do {
                //                boolean hasFrame = false;
                synchronized (CameraEngine.this) {
                    try {
                        while (!mCameraFrameReady && !mStopThread) {
                            CameraEngine.this.wait();
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    if (mCameraFrameReady) {
                        mChainIdx = 1 - mChainIdx;
                        mCameraFrameReady = false;
                        //                        hasFrame = true;
                    }
                }

                //                if (!mStopThread && hasFrame) {
                //processCameraFrame(frameHeight,frameWidth,mFrameChain[1 - mChainIdx].getFrame());
                //                }
            } while (!mStopThread);
            //            Log.d(TAG, "Finish processing thread");
        }
    }


    public void setRenderCallback(CameraView.RenderCallback renderCallback) {
        this.renderCallback = renderCallback;
    }

    public boolean isCameraOpened() {
        return cameraOpened;
    }

    public void focusCamera() {
        synchronized (this) {
            if (camera != null) {
                camera.autoFocus(this);
            }
        }
    }

    public void requestOpenFlashLight(boolean isTorch) {
        synchronized (this) {
            if (camera != null) {
                Camera.Parameters p = camera.getParameters();
                if (isTorch) {
                    p.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);
                } else {
                    p.setFlashMode(Camera.Parameters.FLASH_MODE_AUTO);
                }
                camera.setParameters(p);
            }
        }
    }

    public void requestZoom(double zoomValue) {
        Log.d(TAG, "requestZoom: " + zoomValue + " " + lastZoomValueRec + " " + lastZoomValue);
        synchronized (this) {
            if (camera != null) {
                Camera.Parameters p = camera.getParameters();
                if (p.isZoomSupported()) {
                    lastZoomValueRec += zoomValue;
                    lastZoomValueRec = Math.max(0, Math.min(lastZoomValueRec, 1.0));
                    int curZoom = (int) (lastZoomValueRec * p.getMaxZoom());
                    if (Math.abs(curZoom - lastZoomValue) >= 1) {
                        lastZoomValue = curZoom;
                        p.setZoom(lastZoomValue);
                    }
                } else {
                    return;
                }
                camera.setParameters(p);
            }
        }
    }

    public void requestCloseFlashLight() {
        synchronized (this) {
            if (camera != null) {
                Camera.Parameters p = camera.getParameters();
                p.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
                camera.setParameters(p);
            }
        }
    }

    public void takePhoto() {
        Camera.ShutterCallback shutterCallback = new Camera.ShutterCallback() {
            @Override
            public void onShutter() {
                Log.d(TAG, "onShutter: ");
            }
        };

        Camera.PictureCallback jpegCallback = new Camera.PictureCallback() {
            @Override
            public void onPictureTaken(final byte[] data, final Camera camera) {
                camera.startPreview();
                Log.d(TAG, "onPictureTaken: ");
                pictureTakenCallBack.saveAsBitmap(data);
            }
        };

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            camera.enableShutterSound(false);
        }

        camera.takePicture(shutterCallback, null, jpegCallback);
    }

    public PreviewSize getPreviewSize() {
        return new PreviewSize(frameWidth, frameHeight);
    }

    public void setPreviewSize(PreviewSize previewSize) {
        frameWidth = previewSize.width;
        frameHeight = previewSize.height;
    }

    public class PreviewSize {
        private int width, height;

        public PreviewSize(int width, int height) {
            this.width = width;
            this.height = height;
        }

        public int getWidth() {
            return width;
        }

        public int getHeight() {
            return height;
        }
    }


    public void setPictureTakenCallBack(GLRender.PictureTakenCallBack pictureTakenCallBack) {
        this.pictureTakenCallBack = pictureTakenCallBack;
    }

    public void setPreviewSizeChangedCallback(CameraView.PreviewSizeChangedCallback previewSizeChangedCallback) {
        this.previewSizeChangedCallback = previewSizeChangedCallback;
    }

    private static Camera.Size choosePreferredSize(List<Camera.Size> sizes, double aspectRatio) {
        List<Camera.Size> options = new ArrayList<>();
        for (Camera.Size option : sizes) {
            //            if(option.redPacketWidth==1280 && option.redPacketHeight==720)
            //                return option;
            if (Math.abs((int) (option.height * aspectRatio) - option.width) < 10) {
                options.add(option);
            }
        }
        if (options.size() > 0) {
            return Collections.max(options, new CompareSizesByArea());
        } else {
            return sizes.get(sizes.size() - 1);
        }
    }

    private Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int w, int h) {
        final double ASPECT_TOLERANCE = 0.05;
        double targetRatio = (double) w / h;
        if (sizes == null) {
            return null;
        }
        Camera.Size optimalSize = null;
        double minDiff = Double.MAX_VALUE;
        int targetHeight = h;
        // Try to find an size match aspect ratio and size
        for (Camera.Size size : sizes) {
            double ratio = (double) size.width / size.height;
            if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) {
                continue;
            }
            if (Math.abs(size.height - targetHeight) < minDiff) {
                optimalSize = size;
                minDiff = Math.abs(size.height - targetHeight);
            }
        }
        // Cannot find the one match the aspect ratio, ignore the requirement
        if (optimalSize == null) {
            minDiff = Double.MAX_VALUE;
            for (Camera.Size size : sizes) {
                if (Math.abs(size.height - targetHeight) < minDiff) {
                    optimalSize = size;
                    minDiff = Math.abs(size.height - targetHeight);
                }
            }
        }
        return optimalSize;
    }

    static class CompareSizesByArea implements Comparator<Camera.Size> {
        @Override
        public int compare(Camera.Size lhs, Camera.Size rhs) {
            // We cast here to ensure the multiplications won't overflow
            return Long.signum((long) lhs.width * lhs.height -
                    (long) rhs.width * rhs.height);
        }
    }

    public boolean startRecordingVideo() {
        if (prepareMediaRecorder()) {
            try {
                mMediaRecorder.start();

                return true;
            } catch (Throwable t) {
                t.printStackTrace();
            }
        }
        return false;
    }

    private boolean prepareMediaRecorder() {
        try {
            // final Activity activity = getActivity();
            //if (null == activity) return false;
            //final BaseCaptureInterface captureInterface = (BaseCaptureInterface) activity;

            // setCameraDisplayOrientation(mCamera.getParameters());
            mMediaRecorder = new MediaRecorder();
            camera.stopPreview();
            camera.unlock();
            mMediaRecorder.setCamera(camera);

            //          boolean canUseAudio = true;
            //boolean audioEnabled = !mInterface.audioDisabled();
            //if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
            //    canUseAudio = ContextCompat.checkSelfPermission(activity, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED;

            //            if (canUseAudio && audioEnabled) {
            mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT);
            //            } else if (audioEnabled) {
            //                Toast.makeText(getActivity(), R.string.mcam_no_audio_access, Toast.LENGTH_LONG).show();
            //            }
            mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.DEFAULT);

            final CamcorderProfile profile = CamcorderProfile.get(currentCameraId, CamcorderProfile.QUALITY_HIGH);
            mMediaRecorder.setOutputFormat(profile.fileFormat);
            mMediaRecorder.setVideoFrameRate(profile.videoFrameRate);
            mMediaRecorder.setVideoSize(previewSize.width, previewSize.height);
            mMediaRecorder.setVideoEncodingBitRate(profile.videoBitRate);
            mMediaRecorder.setVideoEncoder(profile.videoCodec);

            mMediaRecorder.setAudioEncodingBitRate(profile.audioBitRate);
            mMediaRecorder.setAudioChannels(profile.audioChannels);
            mMediaRecorder.setAudioSamplingRate(profile.audioSampleRate);
            mMediaRecorder.setAudioEncoder(profile.audioCodec);


            Uri uri = Uri.fromFile(FileUtils.makeTempFile(
                    new File(Environment.getExternalStorageDirectory(),
                            "/Omoshiroi/videos").getAbsolutePath(),
                    "VID_", ".mp4"));

            mMediaRecorder.setOutputFile(uri.getPath());

            //            if (captureInterface.maxAllowedFileSize() > 0) {
            //                mMediaRecorder.setMaxFileSize(captureInterface.maxAllowedFileSize());
            //                mMediaRecorder.setOnInfoListener(new MediaRecorder.OnInfoListener() {
            //                    @Override
            //                    public void onInfo(MediaRecorder mediaRecorder, int what, int extra) {
            //                        if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
            //                            Toast.makeText(getActivity(), R.string.mcam_file_size_limit_reached, Toast.LENGTH_SHORT).show();
            //                            stopRecordingVideo(false);
            //                        }
            //                    }
            //                });
            //            }

            mMediaRecorder.setOrientationHint(90);
            //       mMediaRecorder.setPreviewDisplay(mPreviewView.getHolder().getSurface());

            mMediaRecorder.prepare();
            return true;

        } catch (Exception e) {
            camera.lock();
            e.printStackTrace();
            return false;
        }
    }


    public final void releaseRecorder() {
        if (mMediaRecorder != null) {
            //if (mIsRecording) {
            try {
                mMediaRecorder.stop();
            } catch (Throwable t) {
                //noinspection ResultOfMethodCallIgnored
                //new File(mOutputUri).delete();
                t.printStackTrace();
            }
            //mIsRecording = false;
            //}
            mMediaRecorder.reset();
            mMediaRecorder.release();
            mMediaRecorder = null;
            camera.startPreview();
        }
    }

    public SurfaceTexture getSurfaceTexture() {
        return mSurfaceTexture;
    }

    int displayRotate;

    void initRotateDegree(int cameraId) {
        Camera.CameraInfo info = new Camera.CameraInfo();
        Camera.getCameraInfo(cameraId, info);
        Log.d(TAG, "cameraId: " + cameraId + ", rotation: " + info.orientation);
        int rotation = ((Activity) glRender.getContext()).getWindowManager().getDefaultDisplay().getRotation();
        int degrees = 0;
        switch (rotation) {
            case Surface.ROTATION_0:
                degrees = 0;
                break;
            case Surface.ROTATION_90:
                degrees = 90;
                break;
            case Surface.ROTATION_180:
                degrees = 180;
                break;
            case Surface.ROTATION_270:
                degrees = 270;
                break;
            default:
                break;
        }
        //        displayRotate = (info.orientation - degrees + 360) % 360;
        // 根据Camera设备不同，有可能需要旋转的角度不同
        displayRotate = 90;
    }

    public int getDisplayRotate() {
        return displayRotate;
    }

    public Camera getCamera() {
        return camera;
    }

    public void setGlRender(GLRender glRender) {
        this.glRender = glRender;
    }
}
